#include "settingsdialog.h"

#include "../guicommon.h"

#include <settingtypes.h>
#include <validatormanager.h>

#include <QtGui/QGridLayout>
#include <QtGui/QHBoxLayout>
#include <QtGui/QCheckBox>
#include <QtGui/QLabel>
#include <QtGui/QLineEdit>
#include <QtGui/QPushButton>
#include <QtGui/QSlider>
#include <QtGui/QVBoxLayout>

using namespace core;
using namespace gui;

SettingsDialog::SettingsDialog(Settings* settings,
    bool isModal, QWidget* parent) : QDialog(parent),
        m_controlToValueMapping(),
        m_pendingChanges(),
        m_okButton(0),
        m_acceptButton(0),
        m_cancelButton(0),
        m_categories(0),
        m_pages(0),
        m_settings(settings)
{
    setModal(isModal);
    setWindowTitle(tr("Options"));

    createBasicLayout();
    createPages();
    connectSlots();
}

SettingsDialog::~SettingsDialog()
{

}

void SettingsDialog::createBasicLayout()
{
    m_categories = new QListWidget();
    m_categories->setSizePolicy(QSizePolicy::Fixed,
        QSizePolicy::MinimumExpanding);
    m_pages = new QStackedWidget();

    QFrame* contentWrapper = new QFrame();
    QFrame* buttonsWrapper = new QFrame();

    QVBoxLayout* mainLayout = new QVBoxLayout();

    mainLayout->addWidget(contentWrapper, 1);
    mainLayout->addWidget(buttonsWrapper);

    setLayout(mainLayout);

    const QList<QString> categories = m_settings->categories();

    foreach (const QString& category, categories)
    {
        m_categories->addItem(new QListWidgetItem(category));
        m_pages->addWidget(new QFrame());
    }

    if (categories.size() > 0)
    {
        m_categories->setCurrentRow(0);
    }

    m_categories->setFixedWidth(categories.size() > 0 ?
        m_categories->sizeHintForColumn(0) + 20 : 50);

    QHBoxLayout* contentLayout = new QHBoxLayout();

    contentLayout->addWidget(m_categories);
    contentLayout->addWidget(m_pages);

    contentWrapper->setLayout(contentLayout);

    m_okButton = new QPushButton(tr("OK"));
    m_okButton->setDefault(true);

    m_acceptButton = new QPushButton(tr("Accept"));

    m_cancelButton = new QPushButton(tr("Cancel"));

    QHBoxLayout* buttonsLayout = new QHBoxLayout();

    buttonsLayout->addWidget(m_okButton);
    buttonsLayout->addWidget(m_acceptButton);
    buttonsLayout->addWidget(m_cancelButton);
    buttonsLayout->addStretch();

    buttonsWrapper->setLayout(buttonsLayout);
}

void SettingsDialog::createPages()
{
    const QList<QString> categories = m_settings->categories();

    for (int i = 0; i < categories.size(); ++i)
    {
        const QList<QString> options =
                m_settings->optionsInCategory(categories[i]);

        QWidget* page = m_pages->widget(i);

        QFrame* header = new QFrame();
        header->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

        QLabel* label = new QLabel(m_settings->categoryText(categories[i]));

        QFont font = label->font();
        font.setPointSize(18);
        label->setFont(font);

        QHBoxLayout* headerLayout = new QHBoxLayout();
        headerLayout->setMargin(0);
        headerLayout->setSpacing(0);

        headerLayout->addWidget(label, 0, Qt::AlignTop | Qt::AlignLeft);

        header->setLayout(headerLayout);

        QFrame* content = new QFrame();

        QGridLayout* contentLayout = new QGridLayout();

        int row = 1;
        foreach (const QString& option, options)
        {
            QVariant value = m_settings->optionValue(categories[i], option);

            const QString optionText = m_settings->optionText(
                categories[i], option);
            QLabel* _label = new QLabel(optionText);
            QWidget* _value = 0;

            QCheckBox* chbox = 0;
            QLineEdit* tedit = 0;

            switch (value.userType())
            {
                case QVariant::Bool:
                    chbox = new QCheckBox(optionText);
                    chbox->setChecked(value.toBool());

                    connect(chbox, SIGNAL(toggled(bool)),
                        this, SLOT(changeValue()));

                    _value = chbox;
                    break;
                case QVariant::String:
                case QVariant::Int:
                case QVariant::Double:
                    tedit = new QLineEdit();
                    tedit->setText(value.toString());
                    tedit->setValidator(ValidatorManager::getInstance()->
                        getValidator(value.userType() == QVariant::Int ?
                            INT_PROPERTY_VALIDATOR :
                                value.userType() == QVariant::Double ?
                                    DOUBLE_PROPERTY_VALIDATOR :
                                        STRING_PROPERTY_VALIDATOR).data());

                    connect(tedit, SIGNAL(returnPressed()),
                        this, SLOT(changeValue()));
                    connect(tedit, SIGNAL(editingFinished()),
                        this, SLOT(changeValue()));

                    _value = tedit;
                    break;
                default:
                    if (value.userType() == SettingTypes::typeId("Range"))
                    {
                        QSlider* slider = new QSlider(Qt::Horizontal);

                        Range range = value.value<Range>();
                        slider->setRange(range.Start, range.Stop);
                        slider->setValue(range.Value);
                        slider->setTickPosition(QSlider::TicksBelow);
                        slider->setTickInterval(5);

                        connect(slider, SIGNAL(valueChanged(int)),
                            this, SLOT(changeValue()));

                        _value = slider;
                    }
                    else {
                        Q_ASSERT(false); // not supported
                    }
                    break;
            }

            Q_ASSERT(_value != 0); // not supported value type

            if (value.type() != QVariant::Bool)
            {
                _label->setBuddy(_value);

                contentLayout->addWidget(_label, row, 0,
                    Qt::AlignVCenter | Qt::AlignLeft);
            }
            else
            {
                delete _label;
            }

            _value->setSizePolicy(QSizePolicy::Expanding,
                QSizePolicy::MinimumExpanding);

            contentLayout->addWidget(_value, row,
                (value.type() != QVariant::Bool ? 1 : 0), Qt::AlignVCenter);

            Q_ASSERT(!m_controlToValueMapping.contains(_value)); // bad mapping

            m_controlToValueMapping[_value] = QPair<QString, QString>(
                categories[i], option);

            ++row;
        }

        contentLayout->addItem(new QSpacerItem(0, 0,
            QSizePolicy::Expanding, QSizePolicy::Expanding), row, 0, 1, 2);

        contentLayout->setColumnStretch(1, 1);

        content->setLayout(contentLayout);

        QVBoxLayout* mainLayout = new QVBoxLayout();
        mainLayout->setContentsMargins(10, 0, 10, 0);

        mainLayout->addWidget(header, 0, Qt::AlignTop | Qt::AlignLeft);
        mainLayout->addWidget(content, 1);

        page->setLayout(mainLayout);
    }
}

void SettingsDialog::connectSlots()
{
    connect(m_categories,
        SIGNAL(currentRowChanged(int)),
        this,
        SLOT(changePage(int)));

    connect(m_okButton, SIGNAL(clicked()),
            this, SLOT(accept()));
    connect(m_acceptButton, SIGNAL(clicked()),
            this, SLOT(saveChanges()));
    connect(m_cancelButton, SIGNAL(clicked()),
            this, SLOT(reject()));
}

// public slots
void SettingsDialog::accept()
{
    saveChanges();

    QDialog::accept();
}

void SettingsDialog::reject()
{
    clearChanges();

    QDialog::reject();
}

// private slots
void SettingsDialog::saveChanges()
{
    foreach (QWidget* source, m_pendingChanges.keys())
    {
        Q_ASSERT(m_controlToValueMapping.contains(source)); // not mapped

        QPair<QString, QString> cKey = m_controlToValueMapping[source];
        m_settings->setOptionValue(cKey.first, cKey.second,
            m_pendingChanges[source]);
    }

    m_pendingChanges.clear();
}

void SettingsDialog::clearChanges()
{
    foreach (QWidget* source, m_pendingChanges.keys())
    {
        QCheckBox* chbox = 0;
        QLineEdit* tedit = 0;

        Q_ASSERT(m_controlToValueMapping.contains(source)); // not mapped

        QPair<QString, QString> cKey = m_controlToValueMapping[source];
        QVariant value = m_settings->optionValue(cKey.first, cKey.second);

        switch (value.userType())
        {
            case QVariant::Bool:
                chbox = qobject_cast<QCheckBox*>(source);
                chbox->setChecked(value.toBool());
                break;
            case QVariant::String:
            case QVariant::Int:
            case QVariant::Double:
                tedit = qobject_cast<QLineEdit*>(source);
                tedit->setText(value.toString());
                break;
            default:
                if (value.userType() == SettingTypes::typeId("Range"))
                {
                    QSlider* slider = qobject_cast<QSlider*>(source);

                    Range range = value.value<Range>();
                    slider->setValue(range.Value);
                }
                else {
                    Q_ASSERT(false); // not supported
                }
                break;
        }
    }

    m_pendingChanges.clear();
}

void SettingsDialog::changePage(int index)
{
    m_pages->setCurrentIndex(index);
}

void SettingsDialog::changeValue()
{
    QWidget* _sender = qobject_cast<QWidget*>(sender());

    if (_sender != 0)
    {
        Q_ASSERT(m_controlToValueMapping.contains(_sender)); // not mapped

        const QPair<QString, QString> cKey = m_controlToValueMapping[_sender];
        const QVariant value = m_settings->optionValue(cKey.first,
            cKey.second);
        QVariant _value;

        bool equal = true;

        QCheckBox* chbox = 0;
        QLineEdit* tedit = 0;
        double val = 0;
        switch (value.userType())
        {
            case QVariant::Bool:
                chbox = qobject_cast<QCheckBox*>(_sender);
                equal = value.toBool() == chbox->isChecked();
                _value = QVariant(chbox->isChecked());
                break;
            case QVariant::String:
                tedit = qobject_cast<QLineEdit*>(_sender);
                equal = value.toString() == tedit->text();
                _value = QVariant(tedit->text());
                break;
            case QVariant::Int:
                tedit = qobject_cast<QLineEdit*>(_sender);
                equal = value.toString() == tedit->text();
                _value = QVariant(tedit->text().toInt());
                break;
            case QVariant::Double:
                tedit = qobject_cast<QLineEdit*>(_sender);
                val = tedit->text().toDouble();
                equal = (value.toDouble() - val) < 0.00001;
                _value = QVariant(val);
                break;
            default:
                if (value.userType() == SettingTypes::typeId("Range"))
                {
                    QSlider* slider = qobject_cast<QSlider*>(_sender);

                    Range range = value.value<Range>();
                    range.Value = slider->value();

                    _value = QVariant::fromValue<Range>(range);
                }
                else {
                    Q_ASSERT(false); // not supported
                }
                break;
        }

        if (!equal)
        {
            m_pendingChanges[_sender] = _value;
        }
        else if (m_pendingChanges.contains(_sender))
        {
            m_pendingChanges.remove(_sender);
        }
    }
}
